package demo;

import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.swing.text.html.parser.*;

public class Bookmarks {
	/** The root node the bookmarks are added to. */
	private BookmarkDirectory root;

	/**
	 * Creates a new Bookmarks object, with the entries coming from
	 * <code>path</code>.
	 */
	public Bookmarks(String path) {
		root = new BookmarkDirectory("Bookmarks");
		if (path != null) {
			parse(path);
		}
	}

	/**
	 * Returns the root of the bookmarks.
	 */
	public BookmarkDirectory getRoot() {
		return root;
	}

	/**
	 * Adds the bookmarks in the file at <code>path</code> to the current
	 * root. This creates a ParserDelegator and uses a CallbackHandler to do the
	 * parser.
	 */
	protected void parse(String path) {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(path));

			new ParserDelegator().parse(reader, new CallbackHandler(), true);
		} catch (IOException ioe) {
			System.out.println("IOE: " + ioe);
			JOptionPane.showMessageDialog(null, "Load Bookmarks",
					"Unable to load bookmarks", JOptionPane.ERROR_MESSAGE);
		}
	}

	private static final short NO_ENTRY = 0;
	private static final short BOOKMARK_ENTRY = 2;
	private static final short DIRECTORY_ENTRY = 3;

	/**
	 * The heart of the parsing. The parser parses the data and notifies a
	 * delegate, via the HTMLEditorKit.ParserCallback interface, as the data is
	 * parsed. When CallbackHandler receives notification it creates new
	 * BookmarkEntries or BookmarkDirectories.
	 */
	private class CallbackHandler extends HTMLEditorKit.ParserCallback {
		/** Parent node that new entries are added to. */
		private BookmarkDirectory parent;
		/** The most recently parsed tag. */
		private HTML.Tag tag;
		/** The last tag encountered. */
		private HTML.Tag lastTag;
		/**
		 * The state, will be one of NO_ENTRY, DIRECTORY_ENTRY, or
		 * BOOKMARK_ENTRY.
		 */
		private short state;
		/**
		 * Date for the next BookmarkDirectory node.
		 */
		private Date parentDate;

		/**
		 * The values from the attributes are placed in here. When the text is
		 * encountered this is added to the node hierarchy and a new instance is
		 * created.
		 */
		private BookmarkEntry lastBookmark;

		/**
		 * Creates the CallbackHandler.
		 */
		public CallbackHandler() {
			parent = root;
			lastBookmark = new BookmarkEntry();
		}

		//
		// HTMLEditorKit.ParserCallback methods
		//

		/**
		 * Invoked when text in the html document is encountered. Based on the
		 * current state, this will either: do nothing (state == NO_ENTRY),
		 * create a new BookmarkEntry (state == BOOKMARK_ENTRY) or create a new
		 * BookmarkDirectory (state == DIRECTORY_ENTRY). If state is !=
		 * NO_ENTRY, it is reset to NO_ENTRY after this is invoked.
		 */
		public void handleText(char[] data, int pos) {
			switch (state) {
			case NO_ENTRY:
				break;
			case BOOKMARK_ENTRY:
				// URL.
			{
				lastBookmark.setName(new String(data));
				parent.add(lastBookmark);
				lastBookmark = new BookmarkEntry();
			}
				break;
			case DIRECTORY_ENTRY:
				// directory.
			{
				BookmarkDirectory newParent = new BookmarkDirectory(new String(
						data));
				newParent.setCreated(parentDate);
				parent.add(newParent);
				parent = newParent;
			}
				break;
			default:
				break;
			}
			state = NO_ENTRY;
		}

		/**
		 * Invoked when a start tag is encountered. Based on the tag this may
		 * update the BookmarkEntry and state, or update the parentDate.
		 */
		public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
			lastTag = tag;
			tag = t;
			if (t == HTML.Tag.A && lastTag == HTML.Tag.DT) {
				long lDate;

				// URL
				URL url;
				try {
					url = new URL((String) a.getAttribute(HTML.Attribute.HREF));
				} catch (MalformedURLException murle) {
					url = null;
				}
				lastBookmark.setLocation(url);

				// created
				Date date;
				try {
					lDate = Long.parseLong((String) a.getAttribute("add_date"));
					if (lDate != 0l) {
						date = new Date(1000l * lDate);
					} else {
						date = null;
					}
				} catch (Exception ex) {
					date = null;
				}
				lastBookmark.setCreated(date);

				// last visited
				try {
					lDate = Long.parseLong((String) a
							.getAttribute("last_visit"));
					if (lDate != 0l) {
						date = new Date(1000l * lDate);
					} else {
						date = null;
					}
				} catch (Exception ex) {
					date = null;
				}
				lastBookmark.setLastVisited(date);

				state = BOOKMARK_ENTRY;
			} else if (t == HTML.Tag.H3 && lastTag == HTML.Tag.DT) {
				// new node.
				try {
					parentDate = new Date(1000l * Long.parseLong((String) a
							.getAttribute("add_date")));
				} catch (Exception ex) {
					parentDate = null;
				}
				state = DIRECTORY_ENTRY;
			}
		}

		/**
		 * Invoked when the end of a tag is encountered. If the tag is a DL,
		 * this will set the node that parents are added to the current nodes
		 * parent.
		 */
		public void handleEndTag(HTML.Tag t, int pos) {
			if (t == HTML.Tag.DL && parent != null) {
				parent = (BookmarkDirectory) parent.getParent();
			}
		}
	}

	/**
	 * BookmarkDirectory represents a directory containing other
	 * BookmarkDirectory's as well as BookmarkEntry's. It adds a name and
	 * created property to DefaultMutableTreeNode.
	 */
	public static class BookmarkDirectory extends DefaultMutableTreeNode {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		/** Dates created. */
		private Date created;

		public BookmarkDirectory(String name) {
			super(name);
		}

		public void setName(String name) {
			setUserObject(name);
		}

		public String getName() {
			return (String) getUserObject();
		}

		public void setCreated(Date date) {
			this.created = date;
		}

		public Date getCreated() {
			return created;
		}
	}

	/**
	 * BookmarkEntry represents a bookmark. It contains a URL, a user definable
	 * string, and two dates, one giving the date the URL was last visited and
	 * the other giving the date the bookmark was created.
	 */
	public static class BookmarkEntry extends DefaultMutableTreeNode {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		/** User description of the string. */
		private String name;
		/** The URL the bookmark represents. */
		private URL location;
		/** Dates the URL was last visited. */
		private Date lastVisited;
		/** Date the URL was created. */
		private Date created;

		public void setName(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}

		public void setLocation(URL location) {
			this.location = location;
		}

		public URL getLocation() {
			return location;
		}

		public void setLastVisited(Date date) {
			lastVisited = date;
		}

		public Date getLastVisited() {
			return lastVisited;
		}

		public void setCreated(Date date) {
			this.created = date;
		}

		public Date getCreated() {
			return created;
		}

		public String toString() {
			return getName();
		}
	}
}
